package com.cn.shiro_jwt_redis.filter;

import com.auth0.jwt.exceptions.TokenExpiredException;
import com.cn.shiro_jwt_redis.comm.model.ResultData;
import com.cn.shiro_jwt_redis.comm.utils.JWTUtil;
import com.cn.shiro_jwt_redis.comm.utils.JwtToken;
import com.cn.shiro_jwt_redis.comm.utils.RedisUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Created with IntelliJ IDEA.
 * 鉴权登陆拦截器
 * @Author: 张坤鹏
 * @Date: 2021/08/05/14:58
 * @Description:
 */
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 判断是否允许通过
     * @param request
     * @param response
     * @param mappedValue
     * @return boolean
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response,Object mappedValue){
        System.out.println("isAccessAllowed方法");
        try {
            return executeLogin(request,response);
        } catch (Exception e) {
            log.error("页面没有token，或者强制清楚缓存，请重新登陆");
            responseError(response,"shiro fail");
            return false;
        }
    }

    /**
     * 是否进行登陆请求
     * @param request
     * @param response
     * @return boolean
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        System.out.println("isLoginAttempt方法");
        String token=((HttpServletRequest)request).getHeader("token");
        if (token==null || "null".equals(token)){
            return false;
        }
        return true;
    }

    /**
     * 重写shiro创建token的方法
     * @param request
     * @param response
     * @return authenticationToken
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        System.out.println("createToken方法");
        String jwtToken=((HttpServletRequest)request).getHeader("token");
        if (jwtToken==null || "null".equals(jwtToken)){
            return null;
        }
        return new JwtToken(jwtToken);
    }

    /**
     * isAccessAllowed为false时调用，验证失败
     * @param request
     * @param response
     * @return boolean
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request,ServletResponse response)throws Exception{
        System.out.println("onAccessDenied方法");
        this.sendChallenge(request, response);
        responseError(response,"token verify fail");
        log.error("token验证失败");
        return false;
    }


    /**
     * shiro调用成功后
     * @param token
     * @param subject
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("onLoginSuccess方法");
        String jwtToken=(String)token.getCredentials();
        if (jwtToken!=null || !("null".equals(jwtToken))){
            try {
                if (JWTUtil.verify(jwtToken)){//accessToken还在有效期内
                    System.out.println("token还在有效期内");
                    String account=JWTUtil.getAccount(jwtToken);//用户名
                    Long currentTime=JWTUtil.getCurrentTime(jwtToken);//生成时的时间戳
                    if (redisUtil.hasKey(account)){//判断是否存在该键
                        Long currentTimeRedis=(Long) redisUtil.get(account);//获取键中的value
                        if (currentTime.equals(currentTimeRedis)){//value与生成时间戳对比
                            return true;
                        }
                    }
                }
                return false;
            }catch (Exception e){
                Throwable throwable=e.getCause();
                if (e instanceof TokenExpiredException){
                    System.out.println("token过期后");
                    if (refreshToken(request,response)){
                        return true;
                    }else {
                        return false;
                    }
                }
            }
        }
        return true;
    }


    /**
     * 拦截器的前置方法，对跨域提供支持
     * @param request
     * @param response
     * @return boolean
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("preHandle方法");
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }

        //如果不带token,不去shiro验证
        if (!isLoginAttempt(request,response)){
            responseError(httpServletResponse,"no token");
            return false;
        }
        return super.preHandle(request, response);
    }


    /**
     * 刷新accessToken和refreshToken，并发送新的token返回前端
     * @param request
     * @param response
     * @return boolean
     */
    private boolean refreshToken(ServletRequest request, ServletResponse response){
        System.out.println("refreshToken方法");
        String token=((HttpServletRequest)request).getHeader("token");
        String account=JWTUtil.getAccount(token);//获取用户名
        Long currentTime=JWTUtil.getCurrentTime(token);//获取时间戳
        if (redisUtil.hasKey(account)){//判断redis里面是否存在该键值
            Long currentTimeRedis=(Long)redisUtil.get(account);//获取redis里面的时间戳
            if (currentTime.equals(currentTimeRedis)){
                Long currentTimeNow=System.currentTimeMillis();//当前时间戳
                redisUtil.set(account,currentTimeNow,JWTUtil.REFRESH_EXPIRE_TIME);//刷新refreshToken
                //刷新accessToken
                token=JWTUtil.sign(account,currentTimeNow);
                //发送回前端新的token
                HttpServletResponse httpServletResponse=(HttpServletResponse)response;
                httpServletResponse.setHeader("token",token);
                httpServletResponse.setHeader("Access-Control-Expose-Headers", "token");
                return true;
            }
        }
        return false;
    }

    /**
     * 捕获异常后的处理方案
     * @param response
     * @param msg
     */
    private void responseError(ServletResponse response,String msg){
        HttpServletResponse httpServletResponse=(HttpServletResponse)response;
        httpServletResponse.setStatus(401);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        try {
            String rj=new ObjectMapper().writeValueAsString(new ResultData().code(401).message(msg));
            httpServletResponse.getWriter().append(rj);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
